package eNodeB;

import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Semaphore;

import javax.swing.ImageIcon;

import packets.PacketPDCCH;
import packets.PacketPDSCH;
import packets.RadioPacket;
import radiowaves.RadioWaves;
import ue.UE;
import utils.Duration;
import utils.Horloge;
import xml.ConstanteParseur;
import xml.ParseXMLException;
import Controller.Controller;
import algos.MaxCQI;
import algos.ProportionalFair;
import algos.RoundRobin;
import data.Data;

public class ENodeB implements Observer {
	private final RadioWaves radio;
	private final List<UE> users = new ArrayList<UE>();
	private final Map<UE, List<RessourceBlock>> mapRessourceBlocks = new HashMap<UE, List<RessourceBlock>>();
	private final List<RessourceBlock> poolRessourceBlocksFree = new ArrayList<RessourceBlock>();
	private final List<RessourceBlock> poolRessourceBlocksBusy = new ArrayList<RessourceBlock>();
	private final String MACAddress = "FF:FF:FF:FF:FF:FF";
	private final double positionX;
	private final double positionY;
	private final double rayon;
	private final double minX;
	private final double maxX;
	private final double minY;
	private final double maxY;
	private int maxRessourceBlocks;
	private Map<GregorianCalendar, List<Data>> mapDataTime = new HashMap<GregorianCalendar, List<Data>>();
	private final Semaphore sem;
	private final Duration duration;
	private final ImageIcon img;
	private final Horloge h;
	private Thread algo;
	private final Controller _ctrl = Controller.getController();

	public void freeBusyRessourceBlocks() {
		poolRessourceBlocksFree.addAll(poolRessourceBlocksBusy);
		poolRessourceBlocksBusy.clear();
	}

	public void setMaxRessourceBlock(int maxRessourceBlocks) {
		if (maxRessourceBlocks > 0) {
			this.maxRessourceBlocks = maxRessourceBlocks;
		} else {
			throw new IllegalArgumentException("maxRessourceBlocks <=0");
		}
	}

	public ENodeB(Semaphore sem, int maxRessourceBlocks, double positionX,
			double positionY, double rayon, RadioWaves rw, Duration duration,
			Map<GregorianCalendar, List<Data>> mapDataTime) {
		this.mapDataTime = mapDataTime;
		this.duration = duration;
		this.radio = rw;
		this.sem = sem;
		setMaxRessourceBlock(maxRessourceBlocks);
		this.rayon = rayon;
		this.positionX = positionX;
		this.positionY = positionY;
		this.minX = positionX - rayon;
		this.maxX = positionX + rayon;
		this.minY = positionY - rayon;
		this.maxY = positionY + rayon;
		this.img = new ImageIcon(this.getClass().getResource(
				"/Images/enodeb.png"));

		h = new Horloge(1000, duration);
		h.addObserver(this);

		for (int i = 0; i < maxRessourceBlocks; i++) {
			poolRessourceBlocksFree.add(new RessourceBlock());
		}

	}

	public synchronized RessourceBlock getFirstFreeRessourceBlock() {
		if (poolRessourceBlocksFree.isEmpty()) {
			return null;
		}

		return poolRessourceBlocksFree.get(0);
	}

	public int getPoolRessourceBlocksSize() {
		return poolRessourceBlocksFree.size();
	}

	// private List<UE> getUsers() {
	// return Collections.unmodifiableList(users);
	// }

	public List<RessourceBlock> getFreeRessourceBlocks() {
		return Collections.unmodifiableList(poolRessourceBlocksFree);
	}

	public Map<UE, List<RessourceBlock>> getRessourceBlockMapping() {
		return mapRessourceBlocks;
	}

	public void mapRessourceBlock(UE ue, RessourceBlock rb) {
		poolRessourceBlocksFree.remove(rb);
		poolRessourceBlocksBusy.add(rb);
		if (mapRessourceBlocks.containsKey(ue)) {

			mapRessourceBlocks.get(ue).add(rb);
		} else {
			List<RessourceBlock> rbs = new ArrayList<RessourceBlock>();
			rbs.add(rb);
			mapRessourceBlocks.put(ue, rbs);
		}
	}

	public void sendPacketPDCCH(UE ue, List<RessourceBlock> rbs) {
		RadioPacket rp = new RadioPacket(this.MACAddress, ue.getMAC());
		PacketPDCCH p = new PacketPDCCH(rbs);
		rp.setPayload(p);
		try {
			radio.getSem().acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.getRadio().addPacket(rp);
		radio.getSem().release();
	}

	public void sendPacketPDSCH(Data d) {
		RadioPacket rp = new RadioPacket(this.MACAddress, d.getUE().getMAC());
		PacketPDSCH p = new PacketPDSCH(d);
		rp.setPayload(p);
		try {
			radio.getSem().acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.getRadio().addPacket(rp);
		radio.getSem().release();
	}

	public boolean isInRangeOfENobeB(double x, double y) {
		return x >= minX && x <= maxX && y >= minY && y <= maxY;

	}

	public List<UE> getActualUE() {
		List<UE> ues = new ArrayList<UE>();
		for (UE ue : users) {
			try {
				Fabrique.isInCalendar(ue.getDuration(), h.getactualTime());
				ues.add(ue);
			} catch (ParseXMLException e) {

			}

		}
		return ues;
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		if (arg0 instanceof UE) {
			UE ue = (UE) arg0;
			if (isInRangeOfENobeB(ue.getPositionX(), ue.getPositionY())
					&& !users.contains(ue)) {
				users.add(ue);
				ue.addObserver(this);

				// System.out.println("UE (" + ue.getPositionX() + ";"
				// + ue.getPositionY() + ") added");
			} else if ((String) arg1 == "UEMove") {
				_ctrl.getModel().move();
			} else if (!isInRangeOfENobeB(ue.getPositionX(), ue.getPositionY())
					&& users.contains(ue)) {
				users.remove(ue);
			}

		} else if (arg0 instanceof Horloge) {

			beginAlgorithm();

			for (GregorianCalendar s : mapDataTime.keySet()) {
				if (s.equals(h.getactualTime())) {
					for (Data d : mapDataTime.get(s))
						sendPacketPDSCH(d);
				}
			}

		}

	}

	private void beginAlgorithm() {

		String choice = Fabrique.getFabrique().getTheMap().getAlgorithm();

		if (choice.equals(ConstanteParseur.ROUNDROBIN)) {
			RoundRobin algoRR = new RoundRobin(this);
			algo = new Thread(algoRR);
			algo.start();
			try {
				algo.join();
			} catch (InterruptedException e2) {
				e2.printStackTrace();
			}
		} else if (choice.equals(ConstanteParseur.MAXCQI)) {
			MaxCQI algoMCQI = new MaxCQI(this);
			algo = new Thread(algoMCQI);
			algo.start();
			try {
				algo.join();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
		} else {
			ProportionalFair algoPF = new ProportionalFair(this);
			algo = new Thread(algoPF);
			algo.start();
			try {
				algo.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @return the rayon
	 */
	public double getRayon() {
		return rayon;
	}

	/**
	 * @return the positionY
	 */
	public double getPositionY() {
		return positionY;
	}

	/**
	 * @return the positionX
	 */
	public double getPositionX() {
		return positionX;
	}

	/**
	 * @return the maxRessourceBlocks
	 */
	public int getMaxRessourceBlocks() {
		return maxRessourceBlocks;
	}

	public Semaphore getSem() {
		return sem;
	}

	public RadioWaves getRadio() {
		return radio;
	}

	public ImageIcon getImage() {
		return img;
	}

	public Duration getDuration() {
		return duration;
	}

	public Horloge getHorloge() {
		return h;
	}

	public void freeMapRessourceBlocks() {
		mapRessourceBlocks.clear();
	}

	public List<Data> getDatas(GregorianCalendar g) {
		return mapDataTime.get(g);
	}

}
